package com.bubblegumproject.ogo.remote.io;

import com.bubblegumproject.ogo.action.Action;
import com.bubblegumproject.ogo.action.Input;
import com.bubblegumproject.ogo.action.Output;
import com.bubblegumproject.ogo.component.JavaBeanProperty;
import com.bubblegumproject.ogo.remote.OgoRemoteException;
import org.apache.log4j.Logger;

import java.lang.reflect.Method;

/**
 * @author Azubuko Obele (buko.obele@gmail.com)
 */
public class DefaultActionPacketFactory implements ActionPacketFactory {

    private static final Logger log = Logger.getLogger(DefaultActionPacketFactory.class);

    public ActionPacket createInputPacket(Action action) {
        assert (action != null);
        ActionPacket packet = new ActionPacket();
        packet.setActionClassName(action.getClass().getName());
        packet.setActionSource(action.getSource());
        for (Method method : action.getClass().getMethods()) {
            if (method.isAnnotationPresent(Input.class)) {
                JavaBeanProperty property = new JavaBeanProperty(method);
                packet.set(property.getName(), property.get(action));
            }
        }
        return packet;
    }

    public Action parseInputPacket(ActionPacket packet) {
        assert (packet != null);
        Action action = instantiateAction(packet.getActionClassName());
        action.setSource(packet.getActionSource());
        for (Method method : action.getClass().getMethods()) {
            if (method.isAnnotationPresent(Input.class)) {
                JavaBeanProperty property = new JavaBeanProperty(method);
                Object propertyValue = packet.get(property.getName());
                // TODO: what do we do if the property is missing from the packet?
                property.set(action, propertyValue);
            }
        }

        return action;
    }


    public ActionPacket createOutputPacket(Action action) {
        assert(action != null);
        ActionPacket packet = new ActionPacket();
        packet.setActionClassName(action.getClass().getName());
        packet.setActionSource(action.getSource());
        packet.setActionErrorStack(action.errors());

        // look for all output properties
        for (Method method : action.getClass().getMethods()) {
            if (method.isAnnotationPresent(Output.class)) {
                JavaBeanProperty property = new JavaBeanProperty(method);
                Object propertyValue = property.get(action);
                packet.set(property.getName(), propertyValue);
            }
        }

        return packet;
    }


    public Action parseOutputPacket(ActionPacket packet) {
        assert(packet != null);
        Action action = instantiateAction(packet.getActionClassName());
        action.setSource(packet.getActionSource());
        action.setErrors(packet.getActionErrorStack());

        // initialize all output properties
        for (Method method : action.getClass().getMethods()) {
            if (method.isAnnotationPresent(Output.class)) {
                JavaBeanProperty property = new JavaBeanProperty(method);
                Object propertyValue = packet.get(property.getName());

                property.set(action, propertyValue);
            }
        }

        return action;
    }

    private static Action instantiateAction(String actionClassName) {
        Class actionClazz = null;
        ClassLoader fallback = ActionPacket.class.getClassLoader();

        try {
            actionClazz = Thread.currentThread().getContextClassLoader().loadClass(actionClassName);
        } catch (ClassNotFoundException cnfe) {
            ; // do nothing
        }

        try {
            if (actionClazz == null) actionClazz = fallback.loadClass(actionClassName);
        } catch (ClassNotFoundException cnfe) {
            throw new OgoRemoteException("failed to load action class " + actionClassName, cnfe);
        }

        try {
            return (Action) actionClazz.newInstance();
        } catch (Exception e) {
            throw new OgoRemoteException("failed to instantiate Action class " + actionClazz, e);
        }
    }

}
